/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2021年6月5日
 */
package com.massyframework.beanskin.runtime.framework.installing;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.script.ScriptException;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.massyframework.beanskin.ModuleRuntimeException;
import com.massyframework.beanskin.assembly.Assembly;
import com.massyframework.beanskin.assembly.AssemblyBuilder;
import com.massyframework.beanskin.assembly.ServletModel;
import com.massyframework.beanskin.assembly.beans.BeanContextHandlerFactory;
import com.massyframework.beanskin.service.DependencyService;
import com.massyframework.beanskin.service.DependencyServiceDescriptorBuilder;
import com.massyframework.beanskin.service.ExportService;
import com.massyframework.beanskin.service.ExportServiceDescriptorBuilder;
import com.massyframework.beanskin.service.ServiceFactory;
import com.massyframework.beanskin.service.ServiceProperty;
import com.massyframework.beanskin.service.ServicePropertyType;
import com.massyframework.beanskin.service.ServiceReference;
import com.massyframework.beanskin.service.ServiceRepository;
import com.massyframework.beanskin.util.ServiceLoaderUtils;
import com.massyframework.beanskin.util.classloading.ModuleClassLoader;
import com.massyframework.beanskin.util.exceptioncollect.ExceptionCollectUtils;
import com.massyframework.beanskin.util.localstore.LocalStore;
import com.massyframework.beanskin.util.variables.StringVariableReplacer;
import com.massyframework.beanskin.util.version.VersionRange;

/**
 * bean容器模块解析
 * @author huangkh
 *
 */
public abstract class BeanContextModuleParser<B> extends InitParamsModuleParser<B> {
	
	private Logger logger = LoggerFactory.getLogger(BeanContextModuleParser.class);
	
	protected static final String BEAN_CONTEXT              =  "bean-context";
	protected static final String SERVLET_MODE              =  "servlet-model";
	
	protected static final String CLASS                     = "class";
	protected static final String PATH                      = "path";
	
	protected static final String DEPENDENCYS               = "dependencies";
	protected static final String DEPENDENCY_SERVICE        = "dependency-service";
	protected static final String BEANNAME                  = "beanName";
	protected static final String FILTERSTRING              = "filterString";
	
	protected static final String EXPORTS                   = "exports"; 
	protected static final String EXPORT_SERVICE            = "export-service";
	protected static final String PROPERTIES                = "properties";
	protected static final String PROPERTY                  = "property";
	protected static final String PROPERTY_TYPE             = "type";
	
	private static final String SERVLET_MODEL_LISTENER      = "listener";
	private static final String SERVLET_MODEL_FILTER        = "filter";
	private static final String SERVLET_MODEL_SERVLET       = "servlet";
	
	private static final String CONFIG_TYPES                = "config-types";
	private static final String CONFIG_LOCATIONS            = "config-locations";

	/**
	 * 构造方法
	 * @param kernel {@link Assembly},内核装配件
	 * @param localStore {@link LocalStore}, 本地存储
	 */
	public BeanContextModuleParser(Assembly kernel, LocalStore localStore) {
		super(kernel, localStore);
	}


	@Override
	protected void doParseNodeAfterInitParams(Document doc, Expression<String> expression,  
			StringVariableReplacer replacer, B builder) {
		super.doParseNodeAfterInitParams(doc, expression, replacer, builder);
		NodeList list = doc.getElementsByTagName(BEAN_CONTEXT);
		if (list == null)
			return;
		if (list.getLength() == 0)
			return;
	
		Node node = list.item(0);
		ModuleClassLoader classLoader = this.doParseBeanContext(node, expression, builder);
		
		
		list = doc.getElementsByTagName(DEPENDENCY_SERVICE);
		int size = list.getLength();
		for (int i=0; i<size; i++) {
			Node child = list.item(i);
			this.doParseDependencyService(child, classLoader, expression, builder);
		}
		
		list = doc.getElementsByTagName(EXPORT_SERVICE);
		size = list.getLength();
		for (int i=0; i<size; i++) {
			Node child = list.item(i);
			this.doParseExportService(child, classLoader, expression, builder);
		}
	}

	/**
	 * 解析Bean容器
	 * @param nodes {@link NodeList},节点集合
	 * @param expression {@link Expression},表达式
	 * @param builder {@link B}
	 */
	protected ModuleClassLoader doParseBeanContext(Node node, Expression<String> expression,  B builder) {
		Node beanContextNameNode = node.getAttributes().getNamedItem(NAME);
		String beanContextName = this.getTextContent(beanContextNameNode);

		Node versionRangeNode = node.getAttributes().getNamedItem(VERSION_RANGE);
		String versionRange = this.getTextContent(versionRangeNode);
		if (versionRange == null) {
			versionRange= "0.0.0";
		}

		Node servletModelNode = node.getAttributes().getNamedItem(SERVLET_MODE);
		String servletModel = null;
		if (servletModelNode != null) {
			servletModel = this.getTextContent(servletModelNode);
		}
		
		BeanContextHandlerFactory factory = this.lookupBeanContextHandlerFactory(beanContextName, versionRange);
		if (factory == null) {
			throw new RuntimeException("cannot found BeanContextHandlerFactory: name=" +  beanContextName + ", versionRange=" + versionRange + ".");
		}
		
		//设置Bean容器名称、版本和Servlet模式
		this.setBeanContext(beanContextName, versionRange, factory, this.parseFrom(servletModel), builder);
		
		ModuleClassLoader classLoader = this.createModuleClassLoader(factory, builder);
		
		List<String> configClassNames = null;
		List<String> configLocations = null;

		NodeList nodes = node.getChildNodes();
		int size = nodes.getLength();
		for (int i = 0; i < size; i++) {
			Node childNode = nodes.item(i);
			String nodeName = childNode.getNodeName();
			switch (nodeName) {
				case CONFIG_TYPES: {
					configClassNames = this.getChildNodeValue(childNode, CLASS);
					break;
				}
	
				case CONFIG_LOCATIONS: {
					configLocations = this.getChildNodeValue(childNode, PATH);
					break;
				}
			}
		}
		
		if (configLocations != null && !configLocations.isEmpty()) {
			this.setConfigLocations(configLocations, builder);
		}
		
		if (configClassNames != null && !configClassNames.isEmpty()) {
			List<Class<?>> classes = new ArrayList<>();
			for (String className: configClassNames) {
				try {
					Class<?> annoClass = classLoader.loadClass(className);
					classes.add(annoClass);
					this.doParseAnnotationsWithType(annoClass, expression, builder);
				}catch(Exception e) {
					ModuleRuntimeException exception = new ModuleRuntimeException(
							this.getModuleIdentifier(), e);
					ExceptionCollectUtils.addException(exception);
					throw exception;
				}
			}
			this.setConfigTypes(classes, builder);
		}

		return classLoader;
	}
	
	/**
	 * 针对配置类解析依赖服务注解
	 * @param annotedType {@link Class},被注解的配置类
	 * @param expression {@link Expression},表达式
	 * @param builder {@link B}
	 */
	protected void doParseAnnotationsWithType(Class<?> annotedType, Expression<String> expression, B builder) throws Exception {
		try {
			DependencyService[] dependencyAnnos = annotedType.getAnnotationsByType(DependencyService.class);
			for (DependencyService anno : dependencyAnnos) {
				DependencyServiceDescriptorBuilder descBuilder =
						ServiceLoaderUtils.loadService(DependencyServiceDescriptorBuilder.class, this.getClass().getClassLoader());
				descBuilder.beanName(anno.beanName())
					.requiredType(anno.requireType());
				if (anno.filterString().length() > 0) {
					descBuilder.filterString(anno.filterString());
				}
				this.addDependencyService(descBuilder, builder);
			}
		}catch(Exception e) {
			if (logger.isErrorEnabled()) {
				logger.error(e.getMessage(), e.getCause());
			}
			throw e;
		}
		
		ExportService[] exportAnnos = annotedType.getAnnotationsByType(ExportService.class);
		for (ExportService anno: exportAnnos) {
			ExportServiceDescriptorBuilder descBuilder =
					ServiceLoaderUtils.loadService(ExportServiceDescriptorBuilder.class, this.getClass().getClassLoader());
			descBuilder.beanName(anno.beanName())
				.addExportServices(anno.serviceTypes());
			for (Class<?> type: anno.serviceTypes()) {
				if (ServiceFactory.class.isAssignableFrom(type)) {
					descBuilder.isFactory(true);
					break;
				}
			}
			
			for (ServiceProperty prop: anno.properties()) {
				String value =
                        this.findAndReplace(prop.value(),
                        		data -> {
									try {
										return expression.evaluate(data);
									} catch (ScriptException e) {
										ExceptionCollectUtils.addException(
												new ModuleRuntimeException(this.getModuleIdentifier(), e));
										return data;
									}
								});
				Object propValue = ServicePropertyType.getProperty(prop.propertyType(), value);
				descBuilder.property(prop.name(), propValue);
			}
			this.addExportServie(descBuilder, builder);
		}
	}
	
			
	/**
	 * 解析依赖服务
	 * @param node {@link Node},依赖服务节点
	 * @param classLoader {@link ModuleClassLoader}, 类加载器
	 * @param expression {@link Expression},表达式
	 * @param builder {@link B}
	 */
	protected void doParseDependencyService(Node node, ModuleClassLoader classLoader, Expression<String> expression,  B builder) {
		String beanName = this.getTextContent(node.getAttributes().getNamedItem(BEANNAME));
        String className = null;
        String filterString = null;
        
        NodeList childs = node.getChildNodes();
        int count = childs.getLength();
        for (int j=0; j<count; j++){
            Node child = childs.item(j);
            switch (child.getNodeName()){
                case CLASS:{
                    className = this.getTextContent(child);
                    break;
                }
                case FILTERSTRING: {
                    filterString =
                            this.findAndReplace(this.getTextContent(child),
                                    value -> {
										try {
											return expression.evaluate(value);
										} catch (ScriptException e) {
											ExceptionCollectUtils.addException(
													new ModuleRuntimeException(this.getModuleIdentifier(), e));
											return value;
										}
									});
                    break;
                }
            }
        }
        
        try {
            Class<?> clazz = classLoader.loadClass(className);
            DependencyServiceDescriptorBuilder descBuilder =
            		ServiceLoaderUtils.loadService(DependencyServiceDescriptorBuilder.class, this.getClass().getClassLoader());
            descBuilder.beanName(beanName)
            	.filterString(filterString)
            	.requiredType(clazz);
            this.addDependencyService(descBuilder, builder);
        }catch(ClassNotFoundException e){
            if (logger.isErrorEnabled()){
                logger.error("parser dependency service failed: module="
                        + this.getModuleIdentifier().toIdentifier() + "." , e);
            }
            ModuleRuntimeException newException = new ModuleRuntimeException(this.getModuleIdentifier(),  e);
            
            ExceptionCollectUtils.addException(newException);
            throw newException;
        }
	}
	
	 /**
     * 解析输出服务
     * @param node {@link Node}
     * @param classLoader {@link ModuleClassLoader}, 类加载器
     * @param expression {@link Expression}, 表达式
     * @param builder {@link B}, 构将器
     */
    protected void doParseExportService(Node node, ModuleClassLoader classLoader,
    		Expression<String> expression, B builder){
        String beanName = this.getTextContent(node.getAttributes().getNamedItem(BEANNAME));
        List<String> classNames = new ArrayList<String>();
        Map<String, Object> props = new HashMap<String,Object>();

        NodeList childs = node.getChildNodes();
        int count = childs.getLength();
        for (int j=0; j<count; j++){
            Node child = childs.item(j);
            switch (child.getNodeName()){
                case CLASS :{
                    classNames.add(this.getTextContent(child));
                    break;
                }
                case PROPERTIES: {
                    props = this.doParserProperties(child.getChildNodes(), expression, builder);
                    break;
                }
            }
        }

        try {
            Class<?>[] classes = new Class<?>[classNames.size()];
            boolean isFactory =false;
            for (int k = 0; k < classNames.size(); k++) {
                Class<?> clazz= classLoader.loadClass(classNames.get(k));
                classes[k] = clazz;
                if (ServiceFactory.class.isAssignableFrom(clazz)) {
                	isFactory = true;
                }
            }

            props.put(ServiceReference.EXPORT_TYPES, classes);
            ExportServiceDescriptorBuilder descBuilder =
            		ServiceLoaderUtils.loadService(ExportServiceDescriptorBuilder.class, this.getClass().getClassLoader());
            descBuilder.beanName(beanName)
            	.addExportServices(classes)
            	.isFactory(isFactory)
            	.properties(props);
            
            this.addExportServie(descBuilder, builder);
        }catch(ClassNotFoundException e){
        	if (logger.isErrorEnabled()){
                logger.error("parser dependency service failed: module="
                        + this.getModuleIdentifier().toIdentifier() + "." , e);
            }
            ModuleRuntimeException newException = new ModuleRuntimeException(this.getModuleIdentifier(),e);
            
            ExceptionCollectUtils.addException(newException);
            throw newException;
        }

    }
    
    /**
     * 解析属性
     * @param list {@link List}
     * @param expression {@link Expression}, 表达式
     * @param builder {@link AssemblyBuilder}, 构建器
     * @return {@link Map}
     */
    protected Map<String, Object> doParserProperties(NodeList list, Expression<String> expression, 
    		B builder){
        Map<String, Object> result = new HashMap<String, Object>();
        int size = list.getLength();
        for (int i=0; i<size; i++){
            Node node = list.item(i);
            if (node.getNodeName().equals(PROPERTY)){
                Node attr = node.getAttributes().getNamedItem(NAME);
                String name = this.getTextContent(attr);

                attr = node.getAttributes().getNamedItem(PROPERTY_TYPE);
                String type = attr == null? "string": attr.getTextContent().toLowerCase();
                attr = node.getAttributes().getNamedItem(VALUE);
                String value =
                        this.findAndReplace(this.getTextContent(attr),
                        		data -> {
									try {
										return expression.evaluate(data);
									} catch (ScriptException e) {
										ExceptionCollectUtils.addException(
												new ModuleRuntimeException(this.getModuleIdentifier(),e));
										return data;
									}
								});

                if (value == null){
                    continue;
                }
                
                result.put(name, this.getPropertyValue(type, value));

                //服务排名
                if (name.equals(ServiceReference.SERVICE_RANKING)){
                    result.put(name, Integer.parseInt(value));
                }
            }
        }

        return result;
    }
    
    protected Object getPropertyValue(String type, String value) {
    	 switch(type.toLowerCase()){
         case "stringarr": {
         	String[] arr = StringUtils.split(value, ",");
         	return arr;
         }
         case "int" : {
        	 return Integer.parseInt(value);
         }
         case "short" : {
             return Short.parseShort(value);
         }
         case "boolean" : {
        	 return Boolean.parseBoolean(value);
         }
         case "long" : {
             return Long.parseLong(value);
         }
         case "float" : {
             return Float.parseFloat(value);
         }
         case "double": {
             return Double.parseDouble(value);
         }
         case "char": {
             return value.toCharArray();
         }
         case "byte": {
             return Byte.parseByte(value);
         }
         default:{
        	 return value;
         }
     }
    }

	
	/**
	 * 将{@code text}解析为ServletModel
	 * @param text {@link String}, 文本
	 * @return {@link ServletModel}
	 */
	protected ServletModel parseFrom(String text) {
		if (text == null) return ServletModel.NONE;
		
		if (SERVLET_MODEL_LISTENER.equals(text)) {
			return ServletModel.LISTENER;
		}
		
		if (SERVLET_MODEL_SERVLET.equals(text)) {
			return ServletModel.SERVLET;
		}
		
		if (SERVLET_MODEL_FILTER.equals(text)) {
			return ServletModel.FILTER;
		}
		
		return ServletModel.NONE;
	}
	
	
	/**
	 * 查找满足{@code beanContextName}和{@code versionRange}要求的BeanContextHandlerFactory服务
	 * @param beanContextName {@link String},容器名称
	 * @param versionRange {@link String},版本范围
	 * @return {@link BeanContextHandlerFactory}
	 */
	protected BeanContextHandlerFactory lookupBeanContextHandlerFactory(String beanContextName, String versionRange){
		try {
			VersionRange range = VersionRange.valueOf(versionRange);
			ServiceRepository repo = ServiceRepository.retrieveFrom(this.getKernel());
			List<ServiceReference<BeanContextHandlerFactory>> references =
					repo.getServiceReferences(BeanContextHandlerFactory.class, "(service.name=" + beanContextName + ")");
			Collections.sort(references, (o1, o2) -> {
				return o2.getServiceVersion().compareTo(o1.getServiceVersion());
			});
			
			ServiceReference<BeanContextHandlerFactory> found = null;
			for (ServiceReference<BeanContextHandlerFactory> reference: references) {
				if (range.includes(reference.getServiceVersion())) {
					found = reference;
					break;
				}
			}
			
			return found != null ? repo.getService(found).orElse(null) : null;
		}catch(Exception e) {
			ModuleRuntimeException newException = new ModuleRuntimeException(this.getModuleIdentifier(), e);
			ExceptionCollectUtils.addException(newException);
			throw newException;
		}
	}

	/**
	 * 设置Bean容器
	 * @param beanContextName {@link String}, bean容器名称
	 * @param versionRange {@link String}, 版本范围
	 * @param factory {@link BeanContextHandlerFactory}, bean容器处理器工厂
	 * @param servletModel {@link String},可能为null.
	 * @param builder {@link B}
	 */
	protected abstract void setBeanContext(String beanContextName, String versionRange,
			BeanContextHandlerFactory factory, ServletModel servletMode, B builder);
	
	/**
	 * 设置Bean容器配置文件路经
	 * @param configLocations {@link List}, 配置文件路经，相对于classpath.
	 * @param builder {@link B}
	 */
	protected abstract void setConfigLocations(List<String> configLocations, B builder);
	
	/**
	 * 设置Bean容器配置类型
	 * @param configTypeNames {@link List}, 配置类名集合
	 * @param builder {@link B}
	 */
	protected abstract void setConfigTypes(List<Class<?>> configTypes, B builder);
	
	/**
	 * 创建类加载器
	 * @param factory {@link BeanContextHandlerFactory}, bean容器处理器工厂
	 * @param builder {@link B}
	 * @return {@link ModuleClassLoader}
	 */
	protected abstract ModuleClassLoader createModuleClassLoader(BeanContextHandlerFactory factory, B builder);
	
	/**
	 * 添加依赖服务
	 * @param descBuilder {@link DependencyServiceDescriptorBuilder},依赖服务定义构建器
	 * @param builder {@link B}
	 */
	protected abstract void addDependencyService(DependencyServiceDescriptorBuilder descBuilder, B builder);
	
	/**
	 * 添加输出服务
	 * @param descBuilder {@link ExportServiceDescriptorBuilder}, descBuilder
	 * @param builder {@link B}
	 */
	protected abstract void addExportServie(ExportServiceDescriptorBuilder descBuilder, B builder);
}

